/*
 * Copyright 2009 Original Author(s)
 * 
 * This file is part of Kommando
 * 
 * Kommando is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Kommando is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Kommando.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kommando.core.selector;

import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JEditorPane;
import javax.swing.text.JTextComponent;

import org.kommando.core.HistoryTracker;
import org.kommando.core.KommandoWindow;
import org.kommando.core.action.Action;
import org.kommando.core.action.ActionRepository;
import org.kommando.core.catalog.Catalog;
import org.kommando.core.catalog.CatalogObject;
import org.kommando.core.command.Command;
import org.kommando.core.search.DefaultResult;
import org.kommando.core.search.Result;

/**
 * The {@link CommandSelector} allows selecting a {@link Command}. It uses a {@link KommandoWindow} for the view.
 * 
 * @author Peter De Bruycker
 */
public class CommandSelector {
    private KommandoWindow window;

    private DirectObjectSelector directObjectSelector;
    private ActionSelector actionSelector;
    private IndirectObjectSelector indirectObjectSelector;

    private JTextComponent directObjectTextComponent;
    private JTextComponent indirectObjectTextComponent;

    private AbstractSelector<?> currentSelector;

    public CommandSelector(final Catalog catalog, ActionRepository actionRepository, HistoryTracker historyTracker) {
        this.directObjectSelector = new DirectObjectSelector(this, catalog, historyTracker);
        directObjectSelector.init();

        this.actionSelector = new ActionSelector(this, actionRepository);
        actionSelector.init();

        this.indirectObjectSelector = new IndirectObjectSelector(this, catalog, historyTracker);
        indirectObjectSelector.init();

        directObjectSelector.addSelectorListener(new SelectorListener<CatalogObject>() {
            @Override
            public void selectionChanged(Result<CatalogObject> selection) {
                actionSelector.setDirectObject(selection == null ? null : selection.getValue());
            }
        });

        actionSelector.addSelectorListener(new SelectorListener<Action>() {
            @Override
            public void selectionChanged(Result<Action> selection) {
                indirectObjectSelector.setAction(selection == null ? null : selection.getValue());

                CatalogObject direct = directObjectSelector.getInternalSelection();
                Action action = actionSelector.getInternalSelection();

                if (direct != null && action != null) {
                    if (showIndirectObjectSelector(direct, action)) {
                        CatalogObject indirectObject = action.getDefaultIndirectObjectValue(direct);
                        if (indirectObject != null) {
                            catalog.enhance(indirectObject);
                            indirectObjectSelector.setSelection(new DefaultResult<CatalogObject>(indirectObject));
                        }

                        window.expand();
                    } else {
                        window.shrink();
                    }
                }
            }
        });

        indirectObjectSelector.addSelectorListener(new SelectorListener<CatalogObject>() {
            @Override
            public void selectionChanged(Result<CatalogObject> selection) {
            }
        });

        currentSelector = directObjectSelector;
    }

    private boolean showIndirectObjectSelector(CatalogObject direct, Action action) {
        return action.isIndirectMandatory(direct) || action.isIndirectOptional(direct);
    }

    public ActionCommand getSelectedCommand() {
        CatalogObject direct = directObjectSelector.getInternalSelection();
        Action action = actionSelector.getAction();
        CatalogObject indirect = indirectObjectSelector.getInternalSelection();

        if (direct == null || action == null) {
            return null;
        }

        // verify the indirect object
        if (action.isIndirectMandatory(direct) && indirect == null) {
            return null;
        }

        return new ActionCommand(direct, action, indirect);
    }

    public Map<KommandoWindow.Selector, SelectorState<?>> getState() {
        Map<KommandoWindow.Selector, SelectorState<?>> state = new HashMap<KommandoWindow.Selector, SelectorState<?>>();

        state.put(KommandoWindow.Selector.DIRECT, directObjectSelector.getState());
        state.put(KommandoWindow.Selector.ACTION, actionSelector.getState());
        state.put(KommandoWindow.Selector.INDIRECT, indirectObjectSelector.getState());

        return state;
    }

    public void prepareForDisplay() {
        directObjectSelector.prepareForDisplay();
        actionSelector.prepareForDisplay();
        indirectObjectSelector.prepareForDisplay();

        currentSelector = directObjectSelector;
    }

    public void hide() {
        currentSelector.setActive(false);

        window.hide();
    }

    public void show(boolean clearCurrentSelection) {
        if (clearCurrentSelection) {
            show(null);
        } else {
            window.show();
        }
    }

    private void setCurrentSelector(AbstractSelector<?> selector) {
        currentSelector.setActive(false);
        currentSelector = selector;
        currentSelector.setActive(true);

        window.setActiveSelector(currentSelector.getSelector());

        if (currentSelector == indirectObjectSelector) {
            Action action = actionSelector.getAction();
            if (action.getIndirectTypes().length == 1 && String.class.isAssignableFrom(action.getIndirectTypes()[0])) {
                indirectObjectSelector.setInTextMode(true);
            }
        }
    }

    public void show(Result<CatalogObject> direct) {
        prepareForDisplay();

        setCurrentSelector(directObjectSelector);
        if (direct != null) {
            SelectorState<CatalogObject> state = directObjectSelector.getState();
            state.setSelection(direct);
            state.setResults(Collections.singletonList(direct));

            directObjectSelector.setState(state);
        }

        window.show();
    }

    public boolean isVisible() {
        return window.isVisible();
    }

    public void setWindow(KommandoWindow window) {
        if (this.window != null) {
            this.window.dispose();
        }

        this.window = window;

        directObjectTextComponent = new JEditorPane();
        indirectObjectTextComponent = new JEditorPane();

        directObjectSelector.setTextComponent(directObjectTextComponent);
        indirectObjectSelector.setTextComponent(indirectObjectTextComponent);

        window.shrink();
    }

    public void nextSelector() {
        if (currentSelector.getSelection() == null) {
            return;
        }

        if (currentSelector == directObjectSelector) {
            setCurrentSelector(actionSelector);
        } else if (currentSelector == actionSelector) {
            CatalogObject direct = directObjectSelector.getInternalSelection();
            Action selectedAction = actionSelector.getInternalSelection();
            if (showIndirectObjectSelector(direct, selectedAction)) {
                setCurrentSelector(indirectObjectSelector);
            }
        }
    }

    public void previousSelector() {
        if (currentSelector == actionSelector) {
            setCurrentSelector(directObjectSelector);
        } else if (currentSelector == indirectObjectSelector) {
            setCurrentSelector(actionSelector);
        }
    }

    public boolean handleKeyEvent(KeyEvent e) {
        boolean handled = currentSelector.handleKeyEvent(this, e);

        if (!handled) {
            if (e.getID() == KeyEvent.KEY_RELEASED) {
                if (e.getModifiers() == 0 && e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    hide();
                    return true;
                }

                if (e.getKeyCode() == KeyEvent.VK_TAB) {
                    if (e.isShiftDown()) {
                        previousSelector();
                    } else {
                        nextSelector();
                    }
                    return true;
                }
            }
        }

        return handled;
    }

    public KommandoWindow getWindow() {
        return window;
    }
}
